State of jextract

February 2024

Maurizio Cimadamore

With the finalization of the Foreign Function & Memory API near us, we have spent the last few weeks polishing jextract. We have made several changes, from implementation-only ones (e.g. we now use string templates for all the code generation), to more visible ones, which will affect clients of the code jextract generates. This document is an attempt at summarising the latter kind of changes, so as to provide some basic guidance of the (possible source-breaking) changes you might expect the next time you run jextract .

But, before we dive into the details of what has changed in the jextract tool, it would be useful to remind ourselves the design principles that are behind jextract:

As we shall see, many of the changes described in this document all contribute, each in slightly different ways, to moving jextract harmoniously towards the goals listed above. If you prefer learning what's changed by looking at some code instead, have a look at the (recently re-generated) libclang bindings used by jextract itself. Or, if you just want to get a jextract binary build with the new changes and play with it, please find it here.

Change #1: Source code is the only currency

Perhaps the most obvious difference is that the --source flag is gone. In fact, now jextract always assumes that source code generation is required. After few years of providing both options, it seemed to us that the most idiomatic way to use jextract is to generate bindings for the target platform, using the --source option, and then check in the generated code in some repository. (In fact, this is the approach we use for jextract itself!).

At the same time, the classfile mode was not really adding much: while we started with two separate backends for source vs. classfile generation, we have long moved to a shared backend, so that now the classfile mode is nothing but a more optimized form of source code generation with in-memory compilation. But even this simpler form is not worth the added complexity: after all, compiled classes are just a javac call away.

Change #2: No more constant classes

Anybody who used jextract at least once would know that the tool generates a fairly large amount of classes. Some of these classes are constant classes and are used as a sort of source-level constant pool. That is, these are the classes where the various method handles, var handles, layouts and function descriptors are defined. This means that all other classes that are part of the extracted API, will refer back to constants defined in these classes.

This approach was introduced so as to make startup as good as possible: we didn't want a single native function call to pay the cost for the initialization of all the method handles in a library (as such library can be quite big, as is the case for windows.h). Unfortunately, this also means that the generated code becomes less scrutable. Consider the following struct declaration in C:

If we extract the above declaration with jextract, we obtain something like this:

When looking at this code, it's easy to be confused: we know that Point is a struct, and that it has a layout. But sadly, this layout is not defined in this class, but in one of the constant classes (namely constants$0). And, to make things worse, even simple structs can easily contain dozens of such opaque constants.

This makes the generated code not only hard to read, but also hard to reuse. Consider the case where a developer wants to first generate a struct using jextract, but then copy and paste it in a different code base. Now we have a problem: since the source of the Point class is not self-contained, the developer will also have to follow the dependency chain, and pull in the relevant bits from the constant classes.

This is unfortunate: as mentioned above, generated bindings should be self-evident, self-contained, and easy to copy and paste across code bases. For this reason, we have switched to a different code generation strategy that is more localized. With the latest changes, Point now looks as follows:

The struct layout is now defined in place, thus making the code more readable and self-contained. Of course this is a trade-off: now the layout initialization logic will run as soon as Point is initialized. But typically, since each struct is defined in its own class, there is never a large amount of constants to initialize. In cases where this strategy could lead to issues (e.g. initialization cycles), we resorted to the old good class holder idiom instead.

This means that the code generated by jextract doesn't contain any additional (toplevel) constant classes. Or, in other words, the classes generated by jextract will correspond to API elements in the corresponding C header file. After some extensive testing, we found that this new generation strategy strikes a much better balance between performance and readability of the generated classes.

Change #3: No more RuntimeHelper

There is another class that jextract always generates, namely RuntimeHelper. This class contains several helper functions that are used throughout the generated code. Upon further examination of the methods in this class, it quickly became apparent that most of the functionality in RuntimeHelper has been added at a time where the FFM API was not expressive as it is today. This means that we can safely drop RuntimeHelper without significant loss of functionality, as most of the methods declared there now have a corresponding method in the FFM API. One notable exception is the translation of variadic native functions, which we'll cover in a later section.

Change #4: Simpler layout declarations

Consider the following C struct declaration:

For this struct, jextract generates the following code:

Now, if we follow the reference into the constant class, we find this:

Not only is the layout constant buried away into a constant class: the layout definition contains the definition for the nested Point layout twice! It is easy to see how this fail to scale when a struct contains many nested struct fields.

The correct solution is for layouts to depend on each other by name, like so:

The resulting code is much more readable, contains no repetitions and it more closely matches the corresponding C declaration. One obvious benefit of this strategy is that, should any change be made to the Point layout (e.g. so that ByteOrder.BIG_ENDIAN is used for both fields), such changes are immediately reflected into any other dependent layout (or function descriptor, see below).

Note that, while It is possible, in principle, for initialization cycles to arise when layouts depends on other layouts in this way, in practice that's not a problem as the C language does not allow mutually dependent struct declarations. To have a cycle in one or more struct declarations in C there has to be at least one indirection (pointer).

And simpler function descriptor declarations

The same principle is also applied to the construction of function descriptors - consider the following C function:

The function descriptor for this function goes from this:

To just this:

Change #5: Simpler accessors for global variables and struct fields

Consider the following C global variable declaration:

To allow access to this global variable from Java code, jextract generates two accessors, namely value$get and value$set. A similar pair of accessors is also generated for each struct fields.

The weird accessor name is mostly a relic of the past - a previous incarnation of jextract used to expose three accessors for each variable/field (getter, setter, and reffer, roughly corresponding to the & C operator). Today, we no longer need three accessors, and a simple pair of getter/setter is enough. For this reason, we have decided to simplify jextract to generate accessor names that are consistent with the naming convention followed by Java records. That is, for the above global variable declaration, the following accessors are generated:

Removal of strided accessors

In the case of struct fields, jextract used to generate a further pair of accessor methods, called strided accessors. For instance, given the usual C struct declaration Point (see above) jextract would emit the following extra pair of getter/setter for Point::x:

These strided accessors were especially useful when striding over the contents of a struct array:

Unfortunately, strided accessors can introduce ambiguities when using simpler struct names as the one showed above - more specifically, an strided getter for a field of type long might clash with a plain setter for the same field (as they would both have signature (MemorySegment, long)).

To avoid ambiguities, we have decided to drop strided accessors, and to introduce a slicing helper method instead (Point::asSlice). The above code can then be rewritten like so:

Accessors for composite types

Consider the following global variable declaration:

For a variable of this type, jextract used to generate a single segment accessor:

We have updated jextract to use a more consistent translation scheme that supports composite setters, as well as getters. In other words, for the above global variable declaration, jextract would now generate the following code (similar considerations hold for struct fields whose type is a composite type):

Array element accessors

Finally, let's consider a global variable declaration whose type is an array type:

In this case, jextract did not generate anything special, other than the segment accessor shown above (as the variable type is a composite type). This makes it difficult for clients to access individual array elements, as they would have to either derive array access var handles manually. We have now updated jextract to generate the following array accessors (again, a similar consideration holds for struct fields whose type is an array type):

These new accessors allow clients to access individual elements in the underlying array, simply by providing the required access coordinates (of type long). Note that jextract will also provide access to the size of the array dimensions, via special constant accessors (see below).

Change #6: Improved function pointer interfaces

Translation of functional pointer relies heavily on functional interfaces; this allows jextract to expose handy factories that create an upcall stub segment modelling a function pointer simply from a lambda expression. Consider the following C code:

This function pointer is translated using the following functional interface:

Function pointers are created easily, like so:

Interestingly, the generated code also supports the reverse conversion - that is, going from an upcall stub segment to a functional interface which can be called from Java. This is useful if a native function returns a function pointer:

Unfortunately, doubling down on functional interfaces both for creating new upcall stubs and for wrapping existing ones seems confusing. Also, in order to call a function pointer obtained from native code, we need to wrap the segment into a new instance (and bind it to an existing arena), which is a relatively expensive operation. This also means that jextract now has to generate special functional interface accessors for global variables and struct fields whose type is a function pointer, in case clients prefer to interact with the functional interface directly.

For these reasons, we have simplified the translation strategy for function pointer, and made it more static:

Note that this new scheme retains the strength of the previous translation, and still allows us to create upcall stub segments easily, using lambda expressions. But, instead of providing an ofAddress method which returns a new wrapper, the compare interface now exposes a static method, namely compare::invoke, which can be used to invoke the provided function pointer (modelled as a memory segment, of course). This leads to simpler client code that doesn't require any wrapping:

Thanks to this simplification, jextract no longer needs to generate ad-hoc functional interface accessors. Note that clients can still create functional interfaces that are backed by an upcall stub segment, like so:

Change #7: More efficient translation of variadic functions

A variadic function is a function that can be called with an unspecified number of parameters. For instance:

Here, the format specifier can be followed by zero or more arguments (usually, one for each hole in the format specifier). Modelling variadic function in Java is challenging, because we need a different calling sequence depending on the number and types of arguments passed to the variadic function. This means that, for each variadic call, we might need to create a new specialized downcall method handle.

jextract models variadic function as simple Java varargs method:

While the signature of this method is intuitive, as it closely matches its C counterpart, its implementation is, unfortunately, rather convoluted. As explained above, each new call to this method leads to the creation of a new specialized downcall method handle. More specifically, jextract has to:

  1. unpack the argument Object[] array

  2. infer a layout for each of the variadic argument in the array

  3. create a new specialized downcall method handle, by appending the inferred variadic layouts to the non-variadic function descriptor

  4. invoke the specialized downcall with the provided arguments

It is easy to see how this logic is very convoluted. Even worse, this process has to be repeated for every call to the varargs method - even if a client is only interested in calling the underlying function with the same variadic argument types. It is also worth mentioning that this process is inherently lossy: the process for inferring layouts from actual arguments (see step (3) above) contains some unavoidable heuristics. For instance, what if one argument has type MemorySegment? In this case there's two possible layout choices: an address layout (e.g. the argument is a pointer) or a struct/union layout (e.g. the argument is a struct/union). But we have no good way to disambiguate based of the runtime type of the provided argument. And, even if we did, how would we recover the struct layout associated with the memory segment being processed? Hence, jextract assumes that all memory segments passed as variadic arguments to a variadic function are pointers (e.g. are associated with an address layout).

Ultimately, C variadic functions behave quite differently from their counterparts in Java-land. A C variadic function is closer, in spirit, to a function template, which can be specialized at the call-site, depending on the arguments being passed (and this analogy remains true if we look at how ABIs translate variadic calls into machine code). A Java varargs method, on the other hand, is just a method that allows to pass multiple arguments concisely, hiding the underlying array creation using compile-time desugaring. Indeed, this semantics mismatch is to be blamed for the expressiveness and performance issues discussed above.

To improve this situation, jextract will now translate the above variadic function as follows:

The generated code is now less similar to its C counterpart. For each variadic function, an invoker class is created (not a method!). The invoker class feature a method (printf::apply) whose parameter types are the non-variadic parameters, followed by an Object... for the variadic parameters. This method can be used to perform the variadic call. To create an instance of the invoker class, the printf::makeInvoker factory method must be used. This method takes a list of variadic memory layouts, and return an instance of the invoker class, whose underlying downcall method handle is specialized for the provided layouts:

Alternatively, clients can avoid boxing variadic arguments into a new array, by calling the invoker's downcall method handle directly, like so:

Change #8: Better library loading

The jextract tool supports loading a set of shared libraries that are specified, on the command line, using the -l option. Consider, for example, the following command line, which is used to extract the OpenGL library on Linux/x64:

Here, three libraries are specified to jextract via the -l option, namely GL, GLU and glut. This will cause jextract to insert the following preamble in the generated header class:

This static initializer ensures that the libraries are loaded before any function or global variable in the header class is accessed (which would cause a lookup in the loader lookup).

While this works, relying on System::loadLibrary can lead to issues. First, System::loadLibrary suffers from poor integration with the dynamic linker used by the underlying OS. This means that the above loading requests will likely fail, unless the correct folder is configured, either using LD_LIBRARY_PATH or -Djava.library.path. Secondly, System::loadLibrary associates a library with the current class loader, and prevents the same library from ever be associated with any other classloader.

The FFM API provides a more general library loading mechanism - SymbolLookup::libraryLookup - which is a very thin wrapper around the basic library loading capabilities provided by the OS (e.g. dlopen). This allows clients to deterministically load and unload shared libraries, even across multiple classloaders - while at the same time enjoying better integration with the OS dynamic linker.

For this reason, we have changed the behavior of the -l option to use a library lookup instead. That is, given the above command line, jextract will now set up a symbol lookup that looks like this:

This (composite) lookup will search symbols in:

This lookup behavior ensures maximum flexibility as well as good integration with the underlying OS, and is a much better default for jextract. There might be, however, cases where libraries end up in -Djava.library.path. To support such cases, jextract provides the option --use-system-load-library which reverts the lookup logic to the previous, classloader-based behavior.

Change #9: More constant accessors

The bindings generated by jextract rely on several constants: memory layouts, function descriptors, var handles and method handles. Since these constants can be useful to customize the behavior of the generated bindings, jextract now exposes them in full. For instance, consider the following function declaration in C:

The following constant accessors are generated by jextract:

This might be useful, for instance, for clients that want to adapt the generated method handle. A similar strategy is also used to expose:

In other words, jextract now provides access to all the constants that are used inside the bindings it generates.

Other miscellaneous changes

A number of other minor fixes and enhancements were carried out. We will just list them briefly here: